XplentyでDynamoDBのデータをAppSync経由で扱ってみる
はじめに
データアナリティクス事業本部のkobayashiです。
XplentyはETL、ELTツールとして様々なデータソースを扱え、また処理もXplentyのGUIで直感的に作成できます。 今回はDynamoDBをソースにしたデータを取り扱ってみたいと思います。DynamoDBの場合Xplentyには直接データソースとして接続することはできませんが、AppSyncでGraphQL APIを作成しそのエンドポイントに対してリクエストを送ることでデータを扱う事ができます。
今回はDynamoDBをデータソースとするAppSyncを作成し、XplentyでETL処理してRedshiftへデータを登録してみたいと思います。
変換元となるDynamoDBのデータ
DynamoDBのから移行するデータはDyamoDBのチュートリアルを行うことで登録できるサンプルデータの映画テーブル を対象とします。
テーブルの作成やサンプルデータのロードはAWS公式のドキュメントをご確認ください。
映画テーブルの中身は以下の様になります。
[ { title: "King Kong", year: 1933, info: { image_url: "http://ia.media-imdb.com/images/M/MV5BMTkxOTIxMDU2OV5BMl5BanBnXkFtZTcwNjM5NjQyMg@@._V1_SX400_.jpg", plot: "A film crew goes to a tropical island for an exotic location shoot and discovers a colossal giant gorilla who takes a shine to their female blonde star.", rank: 3551, rating: 8, release_date: "1933-03-07T00:00:00Z", running_time_secs: 6000, actors: [ "Fay Wray", "Robert Armstrong", "Bruce Cabot" ], directors: [ "Merian C. Cooper", "Ernest B. Schoedsack" ], genres: [ "Adventure", "Fantasy", "Horror" ] } }, { title: "Arsenic and Old Lace", year: 1944, info: { image_url: "http://ia.media-imdb.com/images/M/MV5BMTI3NTYyMDA0NV5BMl5BanBnXkFtZTcwMjEwMTMzMQ@@._V1_SX400_.jpg", plot: "A drama critic learns on his wedding day that his beloved maiden aunts are homicidal maniacs, and that insanity runs in his family.", rank: 4025, rating: 8, release_date: "1944-09-01T00:00:00Z", running_time_secs: 7080, actors: [ "Cary Grant", "Priscilla Lane", "Raymond Massey" ], directors: [ "Frank Capra" ], genres: [ "Comedy", "Crime", "Romance", "Thriller" ] } }, ... ]
DynamoDBへ作成した映画テーブルのテーブル名はcm-kobayashi-Movies
で作成したものとして以降の作業を進めます。
AppSyncにDynamoDBをデータソースとするAPIを作成
AWS AppSyncはGraphQLのマネージド・サービスでAmazon DynamoDBを始め、Amazon Aurora、Amazon Elasticsearch Service、AWS Lambda、他のAPIをデータソースとしてAPIを作成し、アプリからGraphQLを使用してデータソースとやり時できます。
今回はデータソースとしてDynamoDBを指定してAPIを作成します。すでに作成してあるDynamoDBをデータソースとするのでインポート機能を使ってAppSync APIを作成します。
手順1-1).DynamoDBテーブルをインポートする
を選択し開始
を押下する。
手順1-2).テーブル名でインポートしたいテーブル(今回はcm-kobayashi-Movies
)を選択し、インポート
を押下してAPIを作成する。
以上でDynamoDBをデータソースとするAppSyncのAPIが作成されます。ただこのままではインポートできない属性があったり、欲しいリゾルバーを作成したりしたいのでスキーマとリゾルバーを修正します。
スキーマの修正
AppSyncのスキーマ設計はAWSの公式ドキュメントに丁寧に書かれているのでこれを読めばすぐに理解できます。
今回はデータの取得だけできれば良いのでQuery
を残してMutation
、Subscriptions
は削除します。
またinfo
属性の型の定義をする必要があるので修正します。加えてキーを指定してデータを取得するクエリ(getCmKobayashiMovies
)とScanに相当するクエリ(listCmKobayashiMovies
)はスキーマ定義に作成されているのですが、DynamoDBのマネージメントコンソールで行える下記の様なクエリ
も欲しかったので定義に加えます。
以下が映画テーブルでクエリを行うスキーマ定義になります。
# (修正)info属性にCmKobayashiMoviesInfoを指定する type CmKobayashiMovies { title: String! year: Int! info: CmKobayashiMoviesInfo } type CmKobayashiMoviesConnection { items: [CmKobayashiMovies] nextToken: String } # (追加)CmKobayashiMoviesInfo型を定義する ## actors、directors、genresは文字列型のリスト ## release_dateは日付型なのでAWSDateTimeを指定 type CmKobayashiMoviesInfo { actors: [String] directors: [String] genres: [String] image_url: String plot: String rank: Int rating: Float release_date: AWSDateTime running_time_secs: Int } # (修正)パーティションキー、ソートキーで検索を行う用にlistCmKobayashiMoviesQueryを追加 type Query { getCmKobayashiMovies(year: Int!, title: String!): CmKobayashiMovies listCmKobayashiMoviesQuery(query: TableCmKobayashiMoviesQueryInput, limit: Int, nextToken: String): CmKobayashiMoviesConnection listCmKobayashiMovies(filter: TableCmKobayashiMoviesFilterInput, limit: Int, nextToken: String): CmKobayashiMoviesConnection } input TableBooleanFilterInput { ne: Boolean eq: Boolean } # (追加)listCmKobayashiMoviesQueryで使うパーティションキー、ソートキーの指定 input TableCmKobayashiMoviesQueryInput { year: TableYearInput! title: TableTitleInput } # (追加)パーティションキーの検索条件の設定 input TableYearInput { eq: Int } # (追加)ソートキーの検索条件の設定 input TableTitleInput { eq: String le: String lt: String ge: String gt: String between: [String] beginsWith: String } input TableCmKobayashiMoviesFilterInput { year: TableIntFilterInput title: TableStringFilterInput } input TableFloatFilterInput { ne: Float eq: Float le: Float lt: Float ge: Float gt: Float contains: Float notContains: Float between: [Float] } input TableIDFilterInput { ne: ID eq: ID le: ID lt: ID ge: ID gt: ID contains: ID notContains: ID between: [ID] beginsWith: ID } input TableIntFilterInput { ne: Int eq: Int le: Int lt: Int ge: Int gt: Int contains: Int notContains: Int between: [Int] } input TableStringFilterInput { ne: String eq: String le: String lt: String ge: String gt: String contains: String notContains: String between: [String] beginsWith: String } input TableStringQueryInput { eq: String le: String lt: String ge: String gt: String between: [String] beginsWith: String }
DynamoDBのリゾルバーの修正
getCmKobayashiMovies
とlistCmKobayashiMovies
のリゾルバーはインポートした際に自動作成されたもので問題ありませんが、listCmKobayashiMoviesQuery
は新規に作成したのでリゾルバーを新しく作成してアタッチする必要があります。リゾルバのマッピングリファレンスもAWSの公式ドキュメントを読めば作成できると思います。
DynamoDB のリゾルバーのマッピングテンプレートリファレンス - AWS AppSync
listCmKobayashiMoviesQuery
のリクエストマッピングテンプレート
Query
リクエストマッピングドキュメントを使うのとquery
セクションにキー条件式を指定しています。
{ "version": "2017-02-28", "operation" : "Query", "query" : $util.transform.toDynamoDBFilterExpression($ctx.args.query), "limit": $util.defaultIfNull($ctx.args.limit, 20), "nextToken": $util.toJson($util.defaultIfNullOrEmpty($ctx.args.nextToken, null)) }
listCmKobayashiMoviesQuery
のレスポンスマッピングテンプレート
こちらもほぼデフォルトのままです。
$util.toJson($ctx.result)
APIのURLとKEYの確認
XplentyからはAPIキーを使ってAppSyncにアクセスするので設定 > プライマリ認証モード
でAPIのURLとAPIキーを確認しておきます。
以上でDynamoDBとAppSyncの設定は終わりましたので後はXplentyからAppSyncに接続してDynamoDBのデータを取得します。
Xplentyのパッケージの実行と確認
Xplentyのパッケージの作成
Xplentyのパッケージの中身は大まかに以下になります。
- AppSync APIにリクエストを送りレスポンスを取得する
- 受け取ったJsonデータを加工してRedshiftへ保存できる形に加工する
- データを保存するRedshiftコンポーネントを作成する
これらのコンポーネントを組み合わせてパッケージを作成していきます。
はじめにサイドバーからPackages
を選択してNew Package
を押下し新しいパッケージを作成します。
1.AppSync APIにリクエストを送りレスポンスを取得する
AppSync APIを使ってデータを取得し、加工・保存を行います。
手順1-2).Add componet
を押下し、SourcesでREST API
を選択する
手順1-3).作成されたアイコンをダブルクリックするとREST APIコンポーネント設定用のモーダルが表示されるので、Authentication
でNone
を選択する。
手順1-4).Request and response
の設定を行いNext
を押下する。
- Method :
POST
を選択 - URL :
{AppSyncの設定で確認したAPI URL}
を入力 -
Headers
- APIキーの指定
- Key :
x-api-key
、 - VALUE :
{AppSyncの設定で確認したAPI KEY}
を入力
- Key :
- Content-Typeの指定
- Key :
Content-Type
、 - VALUE :
application/graphql
を入力
- Key :
- APIキーの指定
- Body :
{ "query": "query listAllMovies { listCmKobayashiMovies(limit:99999) { items { title year info { image_url plot rank rating release_date running_time_secs actors directors genres} } nextToken } }" }
- Use Pagination : チェックしない
- Base record JSONPath Expression :
$.data.listCmKobayashiMovies
を入力
他の設定値はデフォルトのままにします。
補足
Body
はGraphQLの記法とAppSyncで設定したスキーマに従ってGraphQLクエリを記述します。
今回の例ですと、listCmKobayashiMovies
クエリですべての属性を取得しています。
Base record JSONPath Expression
はレスポンスで返ってきたJsonから目的のデータを取得するように設定しています。これは次のResponse Schema
でレスポンスの内容を確認しながら設定すれば大丈夫です。
手順1-6).Response Schema
へ進み、Selected Fields
で右下のAdd
を押下し下記の設定値でSelected Fields
を設定する。その後Save
を押下し、設定を保存する。
Key | Alias | Type |
---|---|---|
items | items | BAG |
この画面ではレスポンスの内容を確認できるので必要なデータを確認しながら設定を行えます。
これでREST APIコンポーネントの設定は終わりますので、次にデータ加工用のコンポーネントをを設定します。
2. 受け取ったJsonデータを加工してRedshiftへ保存できる形に加工する
次にデータを加工してRedshiftへ取り込める状態にします。Selectコンポーネントを2つ繋げて、1つ目のコンポーネントでレスポンスをフラット化し、2つ目のコンポーネントでJson文字列を扱う関数を駆使して欲しい形に加工します。
手順2-1).Add componet
を押下し、TransformationsでSelect
を選択する。この操作を2回行い2つSelectコンポーネントを作成する。
手順2-2).作成したSelectコンポーネントを前段のREST APIコンポーネントと接続する。(接続していないとカラムのマッピングができないため)
手順2-3).作成したSelectコンポーネントの1つ目を選択して設定を行いSave
を押下し保存する。
- EXPRESSION、ALIASに以下のように入力
EXPRESSION | ALIAS |
---|---|
Flatten(items) | item |
ToString(SwitchTimeZone(CurrentTime(),'Asia/Tokyo'), 'yyyy-MM-dd\'T\'HH:mm:ss') | created_datetime |
手順2-4).作成したSelectコンポーネントの2つ目を選択して設定を行いSave
を押下し保存する。
- EXPRESSION、ALIASに以下のように入力
EXPRESSION | ALIAS |
---|---|
ParseIntOrDefault(JsonStringToMap(item)#'year',null) | year |
JsonStringToMap(item)#'title' | title |
ParseIntOrDefault(JsonStringToMap(JsonStringToMap(item)#'info')#'rank',null) | rank |
ParseDoubleOrDefault(JsonStringToMap(JsonStringToMap(item)#'info')#'rating',null) | rating |
ParseIntOrDefault(JsonStringToMap(JsonStringToMap(item)#'info')#'running_time_secs',null) | ParseIntOrDefault(JsonStringToMap(JsonStringToMap(item)#'info')#'running_time_secs',null) |
JsonStringToMap(JsonStringToMap(item)#'info')#'plot' | plot |
JsonStringToMap(JsonStringToMap(item)#'info')#'image_url' | image_url |
ToDate(JsonStringToMap(JsonStringToMap(item)#'info')#'release_date') | release_date |
BagToString(JsonStringToBag(JsonStringToMap(JsonStringToMap(item)#'info')#'actors'),';') | actors |
BagToString(JsonStringToBag(JsonStringToMap(JsonStringToMap(item)#'info')#'directors'),';') | directors |
BagToString(JsonStringToBag(JsonStringToMap(JsonStringToMap(item)#'info')#'genres'),';') | genres |
created_datetime | created_datetime |
actors,directors,genres
は中身を確認した際に文字列型のリストでした。今回は子テーブルを作らず簡単にそのまま;
セミコロンで繋げています。
その際にBAG型を指定した文字列で結合するBAGXplentyのBagToString関数を使用しています。
3. 保存先としてRedshiftコンポーネント作成
前回までと同様に簡単に行うためテーブルは予め作成せずXplentyのジョブで自動作成し、データは毎回洗い替えを行う設定にします。
手順3-1).Add componet
を押下し、DestinationsでAmazon Redshift
を選択する
手順3-2).画面上にRedshiftのアイコンが作成されるので先に作成したSelectコンポーネントのアイコンとDrag&Dropで紐付ける。
手順3-3).RedshiftのアイコンをダブルクリックするとRedshiftコンポーネント設定用のモーダルが表示されるので、設定済みのRedshiftのconnectionを選択する。
手順3-4).移行先のRedshiftのスキーマとテーブルを入力し各種設定を行い、Next
を押下する。
- Automatically create table if it doesn't exist : チェックする
- Automatically add missing columns : チェックする
- Operation type :
Overwrite (Truncate and insert)
を選択する
他の設定はデフォルトで問題ありません。
これでパッケージの作成は完了です。最終的なパッケージの形は下図になります。
Xplentyのパッケージの実行と確認
実際にジョブを実行して結果を確認します。
Redshitでデータを確認
year | title | rank | rating | running_time_secs | plot | image_url | release_date | actors | directors | genres | created_datetime |
---|---|---|---|---|---|---|---|---|---|---|---|
2013 | +1 | 401 | 5.6 | 5700 | Three college friends hit the biggest party of the year, where a mysterious phenomenon disrupts the night, quickly descending into a chaos that challenges their friendships -- and whether they can stay alive. | http://ia.media-imdb.com/images/M/MV5BMTQwOTA5Mzc3Ml5BMl5BanBnXkFtZTgwOTkxODAxMDE@._V1_SX400_.jpg | 2013-03-10 00:00:00.000000 | Rhys Wakefield;Logan Miller;Ashley Hinshaw | Dennis Iliadis | Sci-Fi;Thriller | 2020-07-03T05:11:42 |
2013 | A Madea Christmas | 2374 | NULL | NULL | Madea dispenses her unique form of holiday spirit on rural town when she's coaxed into helping a friend pay her daughter a surprise visit in the country for Christmas. | http://ia.media-imdb.com/images/M/MV5BMTYxMTE0NzMwOV5BMl5BanBnXkFtZTgwNDI1MzkxMDE@._V1_SX400_.jpg | 2013-12-13 00:00:00.000000 | Tyler Perry;Chad Michael Murray;Tika Sumpter | Tyler Perry | Comedy | 2020-07-03T05:11:42 |
2013 | All Is Bright | 464 | 5.3 | 6420 | While out on parole, Dennis reluctantly takes a job selling Christmas trees with his old buddy Rene in order to make enough money to buy his estranged daughter the piano she's always wanted. | http://ia.media-imdb.com/images/M/MV5BMjE2ODg4MDUzOF5BMl5BanBnXkFtZTgwMzk5MjkxMDE@._V1_SX400_.jpg | 2013-04-18 00:00:00.000000 | Paul Giamatti;Paul Rudd;Sally Hawkins | Phil Morrison | Comedy;Drama | 2020-07-03T05:11:42 |
2013 | Battle of the Year | 37 | 3.5 | 6600 | Battle of the Year attracts all the best teams from around the world, but the Americans haven't won in fifteen years. Dante enlists Blake to assemble a team of the best dancers and bring the Trophy back to America where it started. | http://ia.media-imdb.com/images/M/MV5BOTY2OTcyNzM3NF5BMl5BanBnXkFtZTgwNzgxMjkxMDE@._V1_SX400_.jpg | 2013-09-19 00:00:00.000000 | Josh Holloway;Laz Alonso;Josh Peck | Benson Lee | Music | 2020-07-03T05:11:42 |
この様な形でDynamoDBのデータが登録できました。
まとめ
DynamoDBのデータをXplentyで扱うためAppSyncでAPIを作成し、XplentyのREST APIコンポーネントを使ってデータを取得してみました。今回始めてAppSyncを触ってみましたがGraphQL使いやすいですね。積極的に使っていきたいと思います。
最後まで読んで頂いてありがとうございました。